
#include <ql/stream.h>
#include <ql/alloc.h>

#include <string.h>

#define QL_MEM_STREAM_DEFAULT_GROW_SIZE     (4096)

struct QlMemStream
{
    const struct QlStreamVtbl * vtbl;

    bool                        readonly;

    size_t                      pos;

    char **                     data;
    size_t                      size;

    void * (* realloc)(void * p, size_t size);
    void (* free)(void * p);
};

static bool mem_stream_seek(struct QlStream * stream, QlStreamInt offset)
{
    struct QlMemStream * ms = (struct QlMemStream *)stream;
    if (offset < ms->size)
    {
        ms->pos = offset;
        return true;
    }

    return false;
}

static QlStreamInt mem_stream_read(struct QlStream * stream, char * data, QlStreamInt max_size)
{
    struct QlMemStream * ms = (struct QlMemStream *)stream;
    char * src_data = *ms->data;

    if ((ms->pos + max_size) > ms->size)
    {
        max_size = ms->size - ms->pos;
    }

    memcpy(data, src_data + ms->pos, max_size);
    ms->pos += max_size;

    return max_size;
}

static QlStreamInt mem_stream_write(struct QlStream * stream, const char * data, QlStreamInt max_size)
{
    struct QlMemStream * ms = (struct QlMemStream *)stream;
    char * src_data = *ms->data;

    if (ms->readonly)
    {
        return 0;
    }

    // overflow detection
    if ((ms->pos + max_size) > ms->size)
    {
        if (ms->realloc != NULL)
        {
            // realloc buffer
            size_t grow_size = (max_size > QL_MEM_STREAM_DEFAULT_GROW_SIZE ? max_size : QL_MEM_STREAM_DEFAULT_GROW_SIZE);
            ms->size = ms->size + grow_size;
            src_data = ms->realloc(src_data, ms->size);
            *ms->data = src_data;
        }
        else
        {
            // truncate max_size
            max_size = ms->size - ms->pos;
        }
    }

    memcpy(src_data + ms->pos, data, max_size);
    ms->pos += max_size;

    return max_size;
}

static QlStreamInt mem_stream_pos(struct QlStream * stream)
{
    struct QlMemStream * ms = (struct QlMemStream *)stream;
    return ms->pos;
}

static void mem_stream_free(struct QlStream * stream)
{
    struct QlMemStream * ms = (struct QlMemStream *)stream;

    if (ms->free != NULL)
    {
        ms->free(*ms->data);
    }

    ql_free(ms);
}

static const struct QlStreamVtbl g_mem_stream_vtbl =
{
    &mem_stream_seek,
    &mem_stream_read,
    &mem_stream_write,
    &mem_stream_pos,
    &mem_stream_free,
};

struct QlStream * ql_create_memory_stream(char ** data,
                                          size_t initial_size,
                                          bool readonly,
                                          void * (* realloc)(void * p, size_t size),
                                          void (* free)(void * p)
                                          )
{
    struct QlMemStream * result = ql_malloc(sizeof(struct QlMemStream));
    if (data == NULL)
    {
        return 0;
    }

    result->vtbl = &g_mem_stream_vtbl;

    result->pos = 0;
    result->readonly = readonly;
    result->data = data;
    result->size = initial_size;
    result->realloc = realloc;
    result->free = free;

    return (struct QlStream *)result;
}

/*
 * utility functions
 */
bool ql_stream_puts(struct QlStream * stream, const char * str)
{
    size_t len = strlen(str);
    return len == ql_stream_write(stream, str, len);
}
